// SharkFwVersion.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <cstring>
#include <fstream>
#include <time.h>
#include <cassert>
#include "sys/stat.h"

#define FILE_VERSION_H		"version.h"
#define FILE_VERSION_TXT	"version.txt"
#define FILE_VERSION_CFG	"version.cfg"

using namespace std;

static char value2char(int value)
{
	if (value < 10)
	{
		return '0' + value;
	}

	return 'A' + (value - 10);
}

static void parse_line(char *line, char *name, char *value)
{
	char *p = name;

	*name = *value = 0;

	while (1)
	{
		switch (*line)
		{
		case 0:
			*p = 0;
			return;

		case ' ':
		case '\t':
		case '\r':
		case '\f':
		case '\n':
			break;

		case ':':
			*p = 0;
			p = value;
			break;

		default:
			*p++ = *line;
			break;
		}

		line++;
	}
}

static int gen_version(const char *version_cfg, const char *version_h)
{
	ifstream ifs;

	std::cout << "Generate: " << version_h << " <= " << version_cfg << endl;

	ifs.open(version_cfg, ios::binary | ios::in);

	if (!ifs.is_open())
	{
		cerr << "Failed to open " << version_cfg << endl;
		return -1;
	}

	int debug = 0;
	char line[1024];
	char board[1024];
	char project[1024];
	char version[1024];
    char alg_ver[1024];
    char build_num[1024];
	const char *separator = "V";

	board[0] = project[0] = version[0] = 0;

	while (ifs.getline(line, sizeof(line)))
	{
		char key[1024];
		char value[1024];

		parse_line(line, key, value);

		if (strcmp(key, "board") == 0)
		{
			strncpy_s(board, value, sizeof(board));
			continue;
		}

		if (strcmp(key, "project") == 0)
		{
			strncpy_s(project, value, sizeof(project));
			continue;
		}

		if (strcmp(key, "version") == 0)
		{
			strncpy_s(version, value, sizeof(version));
			continue;
		}
        if (strcmp(key, "alg_version") == 0)
        {
            strncpy_s(alg_ver, value, sizeof(alg_ver));
            continue;
        }
        if (strcmp(key, "build_num") == 0)
        {
            strncpy_s(build_num, value, sizeof(build_num));
            continue;
        }
		if (strcmp(key, "debug") == 0)
		{
			debug = atoi(value);
			continue;
		}

		if (strcmp(key, "separator") == 0)
		{
			separator = value;
			continue;
		}
	}

	ifs.close();

	std::cout << "project = " << project << endl;
	std::cout << "version = " << version << endl;

	if (project[0] == 0)
	{
		cerr << "Please config the project";
		return -1;
	}

	if (version[0] == 0)
	{
		cerr << "Please config the version";
		return -1;
	}

	if (debug != 0)
	{
		separator = "@";
	}

	time_t time_now;

	time(&time_now);

	struct tm tm_now;

	localtime_s(&tm_now, &time_now);

	char buff[32];
	char buff_date[32];
	char buff_time[32];

	std::snprintf(buff, sizeof(buff), "%s_%s%s_%c%c%02d%02d%02d", project, separator, version, value2char(tm_now.tm_year - 120), value2char(tm_now.tm_mon + 1), tm_now.tm_mday, tm_now.tm_hour, tm_now.tm_min);
	std::snprintf(buff_date, sizeof(buff_date), "0x%04d%02d%02d", tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday);
	std::snprintf(buff_time, sizeof(buff_time), "0x%02d%02d%02d", tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);

	std::cout << "length = " << strlen(buff) << endl;

	ofstream ofs;

	ofs.open(version_h, ios::binary | ios::out);

	if (!ofs.is_open())
	{
		cerr << "Failed to open " << version_h << endl;
		return -1;
	}

	ofs << "#pragma once" << endl << endl;
	ofs << "#ifndef __" << project << "__" << endl;
	ofs << "#define __" << project << "__" << endl;
	ofs << "#endif" << endl << endl;

	if (board[0] != 0)
	{
		ofs << "#define CONFIG_BOARD_" << board << endl;
	}

	ofs << "#define CONFIG_VERSION          \"" << buff << "\"" << endl;
    ofs << "#define CONFIG_FW_VERSION        " << version << endl;
    ofs << "#define CONFIG_ALG_VERSION       " << alg_ver << endl;
    ofs << "#define CONFIG_BUILD_NUM         " << build_num << endl;
	ofs << "#define CONFIG_BUILD_TIMESTAMP   " << time_now << endl;
	ofs << "#define CONFIG_BUILD_DATE        " << buff_date << endl;
	ofs << "#define CONFIG_BUILD_TIME        " << buff_time << endl;

	ofs << "#define CONFIG_DEBUG             " << debug << endl;

	ofs.close();

	ofs.open(FILE_VERSION_TXT);

	if (!ofs.is_open())
	{
		cerr << "Failed to open " << FILE_VERSION_TXT << endl;
		return -1;
	}

	ofs << buff;

	ofs.close();

	return 0;
}

static bool file_older_then(const char *ipath, const char *opath)
{
	struct _stat istat, ostat;

	if (_stat(ipath, &istat) < 0)
	{
		return false;
	}

	if (_stat(opath, &ostat) < 0)
	{
		return false;
	}

	return istat.st_mtime < ostat.st_mtime;
}

static bool gen_code(const char *in_path, const char *out_path)
{
	bool success = false;

	if (file_older_then(in_path, out_path))
	{
		return true;
	}

	std::cout << "Generate: " << out_path << " <= " << in_path << endl;

	try
	{
		ifstream ifs(in_path, ios::binary | ios::in);
		ofstream ofs(out_path, ios::binary | ios::out | ios::trunc);

		if (ifs && ofs)
		{
			int length = 0;

			while (1)
			{
				char buff[8];
				int value = ifs.get();

				if (value < 0)
				{
					break;
				}

				if (length > 0)
				{
					if (length % 16 != 0)
					{
						ofs << ", ";
					}
					else
					{
						ofs << "," << endl;
					}
				}

				snprintf(buff, sizeof(buff), "%02x", value);
				ofs << "0x" << buff;
				length++;
			}

			cout << "length = " << length << endl;

			if (length > 0 && length % 16 != 1)
			{
				ofs << endl;
			}

			success = true;
		}

		ifs.close();
		ofs.close();
	}
	catch (const std::exception &)
	{
		return success;
	}

	return success;
}

static char *copy_dirname(const char *path, char *buff)
{
	char *sep = NULL;
	char *p = buff;

	while (1)
	{
		switch (*path)
		{
		case 0:
			if (sep == NULL)
			{
				sep = buff;
			}
			else
			{
				sep++;
			}

			*sep = 0;

			return sep;

		case '/':
		case '\\':
			sep = p;
		default:
			*p++ = *path;
			break;
		}

		path++;
	}
}

static const char *get_extension_name(const char *path)
{
	const char *name = NULL;

	while (1)
	{
		switch (*path)
		{
		case 0:
			if (name != NULL)
			{
				return name;
			}

			return path;

		case '.':
			name = path + 1;
			break;

		case '/':
		case '\\':
			name = NULL;
			break;
		}

		path++;
	}

	return NULL;
}

static char *replace_extension_name(const char *path, char *buff, const char *extension)
{
	char *name = NULL;

	while (true)
	{
		char value = *path++;

		switch (value)
		{
		case 0:
			if (name == NULL)
			{
				name = buff;
				*name++ = '.';
			}

			while (*name++ = *extension++);

			return name;

		case '/':
		case '\\':
			name = NULL;
			*buff++ = value;
			break;

		case '.':
			name = buff + 1;
		default:
			*buff++ = value;
			break;
		}
	}
}

static bool copy_file(const char *from, const char *to)
{
	std::cout << "copy: " << from << " => " << to << endl;

	try
	{
		bool success = false;
		ifstream ifs(from, ios::binary | ios::in);
		ofstream ofs(to, ios::binary | ios::out | ios::trunc);

		if (ifs && ofs)
		{
			ofs << ifs.rdbuf();
			success = true;
		}

		ifs.close();
		ofs.close();
	}
	catch (const std::exception &)
	{
		return false;
	}

	return true;
}

static int copy_firmware(const char *path, bool rename)
{
	ifstream ifs;

	ifs.open(FILE_VERSION_TXT, ios::binary | ios::in);

	if (!ifs.is_open())
	{
		cerr << "Failed to open " << FILE_VERSION_TXT << endl;
		return -1;
	}

	char version[1024];

	ifs.getline(version, sizeof(version));

	ifs.close();

	char newpath[4096];

	std::snprintf(copy_dirname(path, newpath), sizeof(newpath), "%s.%s", version, get_extension_name(path));

	if (rename)
	{
		std::cout << "remove: " << newpath << endl;
		remove(newpath);

		std::cout << "rename: " << path << " => " << newpath << endl;
		int ret = ::rename(path, newpath);

		if (ret < 0)
		{
			cerr << "Failed to rename";
			return ret;
		}
	}
	else if (!copy_file(path, newpath))
	{
		cerr << "Failed to copy";
		return -1;
	}

	return 0;
}

static int copy_files(int argc, char *argv[])
{
	for (int index = 1; index < argc; index++)
	{
		if (!copy_file(argv[0], argv[index]))
		{
			cerr << "Failed to copy" << endl;
			return -1;
		}
	}

	return 0;
}

int main(int argc, char *argv[])
{
	if (argc < 2)
	{
		return 0;
	}

	const char *command = argv[1];

	if (strcmp(command, "gen") == 0)
	{
		const char *version_cfg = FILE_VERSION_CFG;
		const char *version_h = FILE_VERSION_H;

		if (argc > 2)
		{
			version_h = argv[2];
		}

		if (argc > 3)
		{
			version_cfg = argv[3];
		}

		return gen_version(version_cfg, version_h);
	}

	if (strcmp(command, "code") == 0)
	{
		const char *in_path;
		char out_path[4096];

		if (argc < 3)
		{
			cerr << "Pleave give the input and output file" << endl;
			return -1;
		}

		in_path = argv[2];

		if (argc > 3)
		{
			strncpy_s(out_path, argv[3], sizeof(out_path));
		}
		else
		{
			replace_extension_name(in_path, out_path, "c");
		}

		if (gen_code(in_path, out_path))
		{
			return 0;
		}

		return -1;
	}

	bool rename = (strcmp(command, "mv") == 0);

	if (rename || strcmp(command, "copy") == 0)
	{
		if (argc > 2)
		{
			return copy_firmware(argv[2], rename);
		}

		cerr << "Pleave give the input file" << endl;

		return -1;
	}

	if (strcmp(command, "mcopy") == 0)
	{
		if (argc > 3)
		{
			return copy_files(argc - 2, argv + 2);
		}

		cerr << "Pleave give the input and output file" << endl;

		return -1;

	}

	return -1;
}
